// Copyright 2022 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include <array>
#include <stdexcept>
#include <string_view>

#include "gtest/gtest.h"
#include "pw_bytes/span.h"
#include "pw_containers/vector.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
#include "pw_status/status_with_size.h"
#include "pw_stream/memory_stream.h"

// These header files contain the code generated by the pw_protobuf plugin.
// They are re-generated every time the tests are built and are used by the
// tests to ensure that the interface remains consistent.
//
// The purpose of the tests in this file is primarily to verify that the
// generated C++ interface is valid rather than the correctness of the
// low-level encoder.
#include "pw_protobuf_test_protos/full_test.pwpb.h"
#include "pw_protobuf_test_protos/importer.pwpb.h"
#include "pw_protobuf_test_protos/non_pw_package.pwpb.h"
#include "pw_protobuf_test_protos/proto2.pwpb.h"
#include "pw_protobuf_test_protos/repeated.pwpb.h"

namespace pw::protobuf {
namespace {

using test::pwpb::Bool;
using test::pwpb::Enum;

namespace DeviceInfo = test::pwpb::DeviceInfo;
namespace KeyValuePair = test::pwpb::KeyValuePair;
namespace Pigweed = test::pwpb::Pigweed;
namespace Proto = test::pwpb::Proto;
namespace RepeatedTest = test::pwpb::RepeatedTest;
namespace TestResult = test::pwpb::TestResult;

namespace imported {
using ::pw::protobuf::test::imported::pwpb::IsValidStatus;
using ::pw::protobuf::test::imported::pwpb::Status;
}  // namespace imported

TEST(Codegen, StreamDecoder) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.magic_number
    0x08, 0x49,
    // pigweed.ziggy
    0x10, 0xdd, 0x01,
    // pigweed.error_message
    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
    // pigweed.bin
    0x40, 0x01,
    // pigweed.pigweed
    0x3a, 0x02,
    // pigweed.pigweed.status
    0x08, 0x02,
    // pigweed.proto
    0x4a, 0x56,
    // pigweed.proto.bin
    0x10, 0x00,
    // pigweed.proto.pigweed_pigweed_bin
    0x18, 0x00,
    // pigweed.proto.pigweed_protobuf_bin
    0x20, 0x01,
    // pigweed.proto.meta
    0x2a, 0x0f,
    // pigweed.proto.meta.file_name
    0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd',
    // pigweed.proto.meta.status
    0x10, 0x02,
    // pigweed.proto.nested_pigweed
    0x0a, 0x3d,
    // pigweed.proto.nested_pigweed.error_message
    0x2a, 0x10, 'h', 'e', 'r', 'e', ' ', 'w', 'e', ' ',
    'g', 'o', ' ', 'a', 'g', 'a', 'i', 'n',
    // pigweed.proto.nested_pigweed.magic_number
    0x08, 0xe8, 0x04,
    // pigweed.proto.nested_pigweed.device_info
    0x32, 0x26,
    // pigweed.proto.nested_pigweed.device_info.attributes[0]
    0x22, 0x10,
    // pigweed.proto.nested_pigweed.device_info.attributes[0].key
    0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
    // pigweed.proto.nested_pigweed.device_info.attributes[0].value
    0x12, 0x05, '5', '.', '3', '.', '1',
    // pigweed.proto.nested_pigweed.device_info.attributes[1]
    0x22, 0x10,
    // pigweed.proto.nested_pigweed.device_info.attributes[1].key
    0x0a, 0x04, 'c', 'h', 'i', 'p',
    // pigweed.proto.nested_pigweed.device_info.attributes[1].value
    0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
    // pigweed.proto.nested_pigweed.device_info.status
    0x18, 0x03,
    // pigweed.id[0]
    0x52, 0x02,
    // pigweed.id[0].id
    0x08, 0x31,
    // pigweed.id[1]
    0x52, 0x02,
    // pigweed.id[1].id
    0x08, 0x39,
    // pigweed.id[2]
    0x52, 0x02,
    // pigweed.id[2].id
    0x08, 0x4b,
    // pigweed.id[3]
    0x52, 0x02,
    // pigweed.id[3].id
    0x08, 0x67,
    // pigweed.id[4]
    0x52, 0x03,
    // pigweed.id[4].id
    0x08, 0x8d, 0x01

  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kMagicNumber);
  Result<uint32_t> magic_number = pigweed.ReadMagicNumber();
  EXPECT_EQ(magic_number.status(), OkStatus());
  EXPECT_EQ(magic_number.value(), 0x49u);

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kZiggy);
  Result<int32_t> ziggy = pigweed.ReadZiggy();
  EXPECT_EQ(ziggy.status(), OkStatus());
  EXPECT_EQ(ziggy.value(), -111);

  constexpr std::string_view kExpectedErrorMessage{"not a typewriter"};

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kErrorMessage);
  std::array<char, 32> error_message{};
  StatusWithSize error_message_status = pigweed.ReadErrorMessage(error_message);
  EXPECT_EQ(error_message_status.status(), OkStatus());
  EXPECT_EQ(error_message_status.size(), kExpectedErrorMessage.size());
  EXPECT_EQ(std::memcmp(error_message.data(),
                        kExpectedErrorMessage.data(),
                        kExpectedErrorMessage.size()),
            0);

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kBin);
  Result<Pigweed::Protobuf::Binary> bin = pigweed.ReadBin();
  EXPECT_EQ(bin.status(), OkStatus());
  EXPECT_EQ(bin.value(), Pigweed::Protobuf::Binary::ZERO);

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kPigweed);
  {
    Pigweed::Pigweed::StreamDecoder pigweed_pigweed =
        pigweed.GetPigweedDecoder();

    EXPECT_EQ(pigweed_pigweed.Next(), OkStatus());
    EXPECT_EQ(pigweed_pigweed.Field().value(),
              Pigweed::Pigweed::Fields::kStatus);
    Result<Bool> pigweed_status = pigweed_pigweed.ReadStatus();
    EXPECT_EQ(pigweed_status.status(), OkStatus());
    EXPECT_EQ(pigweed_status.value(), Bool::FILE_NOT_FOUND);

    EXPECT_EQ(pigweed_pigweed.Next(), Status::OutOfRange());
  }

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kProto);
  {
    Proto::StreamDecoder proto = pigweed.GetProtoDecoder();

    EXPECT_EQ(proto.Next(), OkStatus());
    EXPECT_EQ(proto.Field().value(), Proto::Fields::kBin);
    Result<Proto::Binary> proto_bin = proto.ReadBin();
    EXPECT_EQ(proto_bin.status(), OkStatus());
    EXPECT_EQ(proto_bin.value(), Proto::Binary::OFF);

    EXPECT_EQ(proto.Next(), OkStatus());
    EXPECT_EQ(proto.Field().value(), Proto::Fields::kPigweedPigweedBin);
    Result<Pigweed::Pigweed::Binary> proto_pigweed_bin =
        proto.ReadPigweedPigweedBin();
    EXPECT_EQ(proto_pigweed_bin.status(), OkStatus());
    EXPECT_EQ(proto_pigweed_bin.value(), Pigweed::Pigweed::Binary::ZERO);

    EXPECT_EQ(proto.Next(), OkStatus());
    EXPECT_EQ(proto.Field().value(), Proto::Fields::kPigweedProtobufBin);
    Result<Pigweed::Protobuf::Binary> proto_protobuf_bin =
        proto.ReadPigweedProtobufBin();
    EXPECT_EQ(proto_protobuf_bin.status(), OkStatus());
    EXPECT_EQ(proto_protobuf_bin.value(), Pigweed::Protobuf::Binary::ZERO);

    EXPECT_EQ(proto.Next(), OkStatus());
    EXPECT_EQ(proto.Field().value(), Proto::Fields::kMeta);
    {
      Pigweed::Protobuf::Compiler::StreamDecoder meta = proto.GetMetaDecoder();

      constexpr std::string_view kExpectedFileName{"/etc/passwd"};

      EXPECT_EQ(meta.Next(), OkStatus());
      EXPECT_EQ(meta.Field().value(),
                Pigweed::Protobuf::Compiler::Fields::kFileName);
      std::array<char, 32> meta_file_name{};
      StatusWithSize meta_file_name_status = meta.ReadFileName(meta_file_name);
      EXPECT_EQ(meta_file_name_status.status(), OkStatus());
      EXPECT_EQ(meta_file_name_status.size(), kExpectedFileName.size());
      EXPECT_EQ(std::memcmp(meta_file_name.data(),
                            kExpectedFileName.data(),
                            kExpectedFileName.size()),
                0);

      EXPECT_EQ(meta.Next(), OkStatus());
      EXPECT_EQ(meta.Field().value(),
                Pigweed::Protobuf::Compiler::Fields::kStatus);
      Result<Pigweed::Protobuf::Compiler::Status> meta_status =
          meta.ReadStatus();
      EXPECT_EQ(meta_status.status(), OkStatus());
      EXPECT_EQ(meta_status.value(),
                Pigweed::Protobuf::Compiler::Status::FUBAR);

      EXPECT_EQ(meta.Next(), Status::OutOfRange());
    }

    EXPECT_EQ(proto.Next(), OkStatus());
    EXPECT_EQ(proto.Field().value(), Proto::Fields::kPigweed);
    {
      Pigweed::StreamDecoder proto_pigweed = proto.GetPigweedDecoder();

      constexpr std::string_view kExpectedProtoErrorMessage{"here we go again"};

      EXPECT_EQ(proto_pigweed.Next(), OkStatus());
      EXPECT_EQ(proto_pigweed.Field().value(), Pigweed::Fields::kErrorMessage);
      std::array<char, 32> proto_pigweed_error_message{};
      StatusWithSize proto_pigweed_error_message_status =
          proto_pigweed.ReadErrorMessage(proto_pigweed_error_message);
      EXPECT_EQ(proto_pigweed_error_message_status.status(), OkStatus());
      EXPECT_EQ(proto_pigweed_error_message_status.size(),
                kExpectedProtoErrorMessage.size());
      EXPECT_EQ(std::memcmp(proto_pigweed_error_message.data(),
                            kExpectedProtoErrorMessage.data(),
                            kExpectedProtoErrorMessage.size()),
                0);

      EXPECT_EQ(proto_pigweed.Next(), OkStatus());
      EXPECT_EQ(proto_pigweed.Field().value(), Pigweed::Fields::kMagicNumber);
      Result<uint32_t> proto_pigweed_magic_number =
          proto_pigweed.ReadMagicNumber();
      EXPECT_EQ(proto_pigweed_magic_number.status(), OkStatus());
      EXPECT_EQ(proto_pigweed_magic_number.value(), 616u);

      EXPECT_EQ(proto_pigweed.Next(), OkStatus());
      EXPECT_EQ(proto_pigweed.Field().value(), Pigweed::Fields::kDeviceInfo);
      {
        DeviceInfo::StreamDecoder device_info =
            proto_pigweed.GetDeviceInfoDecoder();

        EXPECT_EQ(device_info.Next(), OkStatus());
        EXPECT_EQ(device_info.Field().value(), DeviceInfo::Fields::kAttributes);
        {
          KeyValuePair::StreamDecoder key_value_pair =
              device_info.GetAttributesDecoder();

          constexpr std::string_view kExpectedKey{"version"};
          constexpr std::string_view kExpectedValue{"5.3.1"};

          EXPECT_EQ(key_value_pair.Next(), OkStatus());
          EXPECT_EQ(key_value_pair.Field().value(), KeyValuePair::Fields::kKey);
          std::array<char, 32> key{};
          StatusWithSize key_status = key_value_pair.ReadKey(key);
          EXPECT_EQ(key_status.status(), OkStatus());
          EXPECT_EQ(key_status.size(), kExpectedKey.size());
          EXPECT_EQ(
              std::memcmp(key.data(), kExpectedKey.data(), kExpectedKey.size()),
              0);

          EXPECT_EQ(key_value_pair.Next(), OkStatus());
          EXPECT_EQ(key_value_pair.Field().value(),
                    KeyValuePair::Fields::kValue);
          std::array<char, 32> value{};
          StatusWithSize value_status = key_value_pair.ReadValue(value);
          EXPECT_EQ(value_status.status(), OkStatus());
          EXPECT_EQ(value_status.size(), kExpectedValue.size());
          EXPECT_EQ(
              std::memcmp(
                  value.data(), kExpectedValue.data(), kExpectedValue.size()),
              0);

          EXPECT_EQ(key_value_pair.Next(), Status::OutOfRange());
        }

        EXPECT_EQ(device_info.Next(), OkStatus());
        EXPECT_EQ(device_info.Field().value(), DeviceInfo::Fields::kAttributes);
        {
          KeyValuePair::StreamDecoder key_value_pair =
              device_info.GetAttributesDecoder();

          constexpr std::string_view kExpectedKey{"chip"};
          constexpr std::string_view kExpectedValue{"left-soc"};

          EXPECT_EQ(key_value_pair.Next(), OkStatus());
          EXPECT_EQ(key_value_pair.Field().value(), KeyValuePair::Fields::kKey);
          std::array<char, 32> key{};
          StatusWithSize key_status = key_value_pair.ReadKey(key);
          EXPECT_EQ(key_status.status(), OkStatus());
          EXPECT_EQ(key_status.size(), kExpectedKey.size());
          EXPECT_EQ(
              std::memcmp(key.data(), kExpectedKey.data(), kExpectedKey.size()),
              0);

          EXPECT_EQ(key_value_pair.Next(), OkStatus());
          EXPECT_EQ(key_value_pair.Field().value(),
                    KeyValuePair::Fields::kValue);
          std::array<char, 32> value{};
          StatusWithSize value_status = key_value_pair.ReadValue(value);
          EXPECT_EQ(value_status.status(), OkStatus());
          EXPECT_EQ(value_status.size(), kExpectedValue.size());
          EXPECT_EQ(
              std::memcmp(
                  value.data(), kExpectedValue.data(), kExpectedValue.size()),
              0);

          EXPECT_EQ(key_value_pair.Next(), Status::OutOfRange());
        }

        EXPECT_EQ(device_info.Next(), OkStatus());
        EXPECT_EQ(device_info.Field().value(), DeviceInfo::Fields::kStatus);
        Result<DeviceInfo::DeviceStatus> device_info_status =
            device_info.ReadStatus();
        EXPECT_EQ(device_info_status.status(), OkStatus());
        EXPECT_EQ(device_info_status.value(), DeviceInfo::DeviceStatus::PANIC);

        EXPECT_EQ(device_info.Next(), Status::OutOfRange());
      }

      EXPECT_EQ(proto_pigweed.Next(), Status::OutOfRange());
    }

    EXPECT_EQ(proto.Next(), Status::OutOfRange());
  }

  for (int i = 0; i < 5; ++i) {
    EXPECT_EQ(pigweed.Next(), OkStatus());
    EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kId);

    Proto::ID::StreamDecoder id = pigweed.GetIdDecoder();

    EXPECT_EQ(id.Next(), OkStatus());
    EXPECT_EQ(id.Field().value(), Proto::ID::Fields::kId);
    Result<uint32_t> id_id = id.ReadId();
    EXPECT_EQ(id_id.status(), OkStatus());
    EXPECT_EQ(id_id.value(), 5u * i * i + 3 * i + 49);

    EXPECT_EQ(id.Next(), Status::OutOfRange());
  }

  EXPECT_EQ(pigweed.Next(), Status::OutOfRange());
}

TEST(Codegen, ResourceExhausted) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.error_message
    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kErrorMessage);
  std::array<char, 8> error_message{};
  StatusWithSize error_message_status = pigweed.ReadErrorMessage(error_message);
  EXPECT_EQ(error_message_status.status(), Status::ResourceExhausted());
  EXPECT_EQ(error_message_status.size(), 0u);

  EXPECT_EQ(pigweed.Next(), Status::OutOfRange());
}

TEST(Codegen, BytesReader) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.error_message
    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  constexpr std::string_view kExpectedErrorMessage{"not a typewriter"};

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kErrorMessage);
  {
    StreamDecoder::BytesReader bytes_reader = pigweed.GetErrorMessageReader();
    EXPECT_EQ(bytes_reader.field_size(), kExpectedErrorMessage.size());

    std::array<std::byte, 32> error_message{};
    Result<ByteSpan> result = bytes_reader.Read(error_message);
    EXPECT_EQ(result.status(), OkStatus());
    EXPECT_EQ(result.value().size(), kExpectedErrorMessage.size());
    EXPECT_EQ(std::memcmp(result.value().data(),
                          kExpectedErrorMessage.data(),
                          kExpectedErrorMessage.size()),
              0);

    result = bytes_reader.Read(error_message);
    EXPECT_EQ(result.status(), Status::OutOfRange());
  }

  EXPECT_EQ(pigweed.Next(), Status::OutOfRange());
}

TEST(Codegen, Enum) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.bin (valid value)
    0x40, 0x01,
    // pigweed.bin (unknown value)
    0x40, 0x7f,
    // pigweed.bin (invalid value)
    0x40, 0xff,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kBin);
  Result<Pigweed::Protobuf::Binary> bin = pigweed.ReadBin();
  EXPECT_EQ(bin.status(), OkStatus());
  EXPECT_TRUE(Pigweed::Protobuf::IsValidBinary(bin.value()));
  EXPECT_EQ(bin.value(), Pigweed::Protobuf::Binary::ZERO);

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kBin);
  bin = pigweed.ReadBin();
  EXPECT_EQ(bin.status(), OkStatus());
  EXPECT_FALSE(Pigweed::Protobuf::IsValidBinary(bin.value()));
  EXPECT_EQ(static_cast<uint32_t>(bin.value()), 0x7fu);

  EXPECT_EQ(pigweed.Next(), OkStatus());
  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::kBin);
  bin = pigweed.ReadBin();
  EXPECT_EQ(bin.status(), Status::DataLoss());
}

TEST(Codegen, ImportedEnum) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // result.status (valid value)
    0x08, 0x01,
    // result.status (unknown value)
    0x08, 0x7f,
    // result.status (invalid value)
    0x08, 0xff,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  TestResult::StreamDecoder test_result(reader);

  EXPECT_EQ(test_result.Next(), OkStatus());
  EXPECT_EQ(test_result.Field().value(), TestResult::Fields::kStatus);
  Result<imported::Status> status = test_result.ReadStatus();
  EXPECT_EQ(status.status(), OkStatus());
  EXPECT_TRUE(imported::IsValidStatus(status.value()));
  EXPECT_EQ(status.value(), imported::Status::NOT_OK);

  EXPECT_EQ(test_result.Next(), OkStatus());
  EXPECT_EQ(test_result.Field().value(), TestResult::Fields::kStatus);
  status = test_result.ReadStatus();
  EXPECT_EQ(status.status(), OkStatus());
  EXPECT_FALSE(imported::IsValidStatus(status.value()));
  EXPECT_EQ(static_cast<uint32_t>(status.value()), 0x7fu);

  EXPECT_EQ(test_result.Next(), OkStatus());
  EXPECT_EQ(test_result.Field().value(), TestResult::Fields::kStatus);
  status = test_result.ReadStatus();
  EXPECT_EQ(status.status(), Status::DataLoss());
}

TEST(CodegenRepeated, NonPackedScalar) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x08, 0x00,
    0x08, 0x10,
    0x08, 0x20,
    0x08, 0x30,
    // fixed32s[]. v={0, 16, 32, 48}
    0x35, 0x00, 0x00, 0x00, 0x00,
    0x35, 0x10, 0x00, 0x00, 0x00,
    0x35, 0x20, 0x00, 0x00, 0x00,
    0x35, 0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(repeated_test.Next(), OkStatus());
    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);

    Result<uint32_t> result = repeated_test.ReadUint32s();
    EXPECT_EQ(result.status(), OkStatus());
    EXPECT_EQ(result.value(), i * 16u);
  }

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(repeated_test.Next(), OkStatus());
    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);

    Result<uint32_t> result = repeated_test.ReadFixed32s();
    EXPECT_EQ(result.status(), OkStatus());
    EXPECT_EQ(result.value(), i * 16u);
  }

  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
}

TEST(CodegenRepeated, NonPackedScalarVector) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x08, 0x00,
    0x08, 0x10,
    0x08, 0x20,
    0x08, 0x30,
    // fixed32s[]. v={0, 16, 32, 48}
    0x35, 0x00, 0x00, 0x00, 0x00,
    0x35, 0x10, 0x00, 0x00, 0x00,
    0x35, 0x20, 0x00, 0x00, 0x00,
    0x35, 0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  pw::Vector<uint32_t, 8> uint32s{};

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(repeated_test.Next(), OkStatus());
    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);

    Status status = repeated_test.ReadUint32s(uint32s);
    EXPECT_EQ(status, OkStatus());
    EXPECT_EQ(uint32s.size(), i + 1u);
  }

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(uint32s[i], i * 16u);
  }

  pw::Vector<uint32_t, 8> fixed32s{};

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(repeated_test.Next(), OkStatus());
    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);

    Status status = repeated_test.ReadFixed32s(fixed32s);
    EXPECT_EQ(status, OkStatus());
    EXPECT_EQ(fixed32s.size(), i + 1u);
  }

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(fixed32s[i], i * 16u);
  }

  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
}

TEST(CodegenRepeated, NonPackedVarintScalarVectorFull) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x08, 0x00,
    0x08, 0x10,
    0x08, 0x20,
    0x08, 0x30,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  pw::Vector<uint32_t, 2> uint32s{};

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);
  Status status = repeated_test.ReadUint32s(uint32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(uint32s.size(), 1u);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);
  status = repeated_test.ReadUint32s(uint32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(uint32s.size(), 2u);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);
  status = repeated_test.ReadUint32s(uint32s);
  EXPECT_EQ(status, Status::ResourceExhausted());
  EXPECT_EQ(uint32s.size(), 2u);

  for (int i = 0; i < 2; ++i) {
    EXPECT_EQ(uint32s[i], i * 16u);
  }
}

TEST(CodegenRepeated, NonPackedFixedScalarVectorFull) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // fixed32s[]. v={0, 16, 32, 48}
    0x35, 0x00, 0x00, 0x00, 0x00,
    0x35, 0x10, 0x00, 0x00, 0x00,
    0x35, 0x20, 0x00, 0x00, 0x00,
    0x35, 0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  pw::Vector<uint32_t, 2> fixed32s{};

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);
  Status status = repeated_test.ReadFixed32s(fixed32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(fixed32s.size(), 1u);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);
  status = repeated_test.ReadFixed32s(fixed32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(fixed32s.size(), 2u);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);
  status = repeated_test.ReadFixed32s(fixed32s);
  EXPECT_EQ(status, Status::ResourceExhausted());
  EXPECT_EQ(fixed32s.size(), 2u);

  for (int i = 0; i < 2; ++i) {
    EXPECT_EQ(fixed32s[i], i * 16u);
  }
}

TEST(CodegenRepeated, PackedScalar) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x0a, 0x04,
    0x00,
    0x10,
    0x20,
    0x30,
    // fixed32s[]. v={0, 16, 32, 48}
    0x32, 0x10,
    0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x20, 0x00, 0x00, 0x00,
    0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);
  std::array<uint32_t, 8> uint32s{};
  StatusWithSize sws = repeated_test.ReadUint32s(uint32s);
  EXPECT_EQ(sws.status(), OkStatus());
  EXPECT_EQ(sws.size(), 4u);

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(uint32s[i], i * 16u);
  }

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);
  std::array<uint32_t, 8> fixed32s{};
  sws = repeated_test.ReadFixed32s(fixed32s);
  EXPECT_EQ(sws.status(), OkStatus());
  EXPECT_EQ(sws.size(), 4u);

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(fixed32s[i], i * 16u);
  }

  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
}

TEST(CodegenRepeated, PackedVarintScalarExhausted) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x0a, 0x04,
    0x00,
    0x10,
    0x20,
    0x30,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);
  std::array<uint32_t, 2> uint32s{};
  StatusWithSize sws = repeated_test.ReadUint32s(uint32s);
  EXPECT_EQ(sws.status(), Status::ResourceExhausted());
  EXPECT_EQ(sws.size(), 2u);

  for (int i = 0; i < 2; ++i) {
    EXPECT_EQ(uint32s[i], i * 16u);
  }
}

TEST(CodegenRepeated, PackedFixedScalarExhausted) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // fixed32s[]. v={0, 16, 32, 48}
    0x32, 0x10,
    0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x20, 0x00, 0x00, 0x00,
    0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);
  std::array<uint32_t, 2> fixed32s{};
  StatusWithSize sws = repeated_test.ReadFixed32s(fixed32s);
  EXPECT_EQ(sws.status(), Status::ResourceExhausted());
  EXPECT_EQ(sws.size(), 0u);
}

TEST(CodegenRepeated, PackedScalarVector) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x0a, 0x04,
    0x00,
    0x10,
    0x20,
    0x30,
    // fixed32s[]. v={0, 16, 32, 48}
    0x32, 0x10,
    0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x20, 0x00, 0x00, 0x00,
    0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);
  pw::Vector<uint32_t, 8> uint32s{};
  Status status = repeated_test.ReadUint32s(uint32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(uint32s.size(), 4u);

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(uint32s[i], i * 16u);
  }

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);
  pw::Vector<uint32_t, 8> fixed32s{};
  status = repeated_test.ReadFixed32s(fixed32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(fixed32s.size(), 4u);

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(fixed32s[i], i * 16u);
  }

  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
}

TEST(CodegenRepeated, PackedVarintScalarVectorFull) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x0a, 0x04,
    0x00,
    0x10,
    0x20,
    0x30,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);
  pw::Vector<uint32_t, 2> uint32s{};
  Status status = repeated_test.ReadUint32s(uint32s);
  EXPECT_EQ(status, Status::ResourceExhausted());
  EXPECT_EQ(uint32s.size(), 2u);

  for (int i = 0; i < 2; ++i) {
    EXPECT_EQ(uint32s[i], i * 16u);
  }
}

TEST(CodegenRepeated, PackedFixedScalarVectorFull) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // fixed32s[]. v={0, 16, 32, 48}
    0x32, 0x10,
    0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x20, 0x00, 0x00, 0x00,
    0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);
  pw::Vector<uint32_t, 2> fixed32s{};
  Status status = repeated_test.ReadFixed32s(fixed32s);
  EXPECT_EQ(status, Status::ResourceExhausted());
  EXPECT_EQ(fixed32s.size(), 0u);
}

TEST(CodegenRepeated, PackedScalarVectorRepeated) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x0a, 0x04,
    0x00,
    0x10,
    0x20,
    0x30,
    // uint32s[], v={64, 80, 96, 112}
    0x0a, 0x04,
    0x40,
    0x50,
    0x60,
    0x70,
    // fixed32s[]. v={0, 16, 32, 48}
    0x32, 0x10,
    0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x20, 0x00, 0x00, 0x00,
    0x30, 0x00, 0x00, 0x00,
    // fixed32s[]. v={64, 80, 96, 112}
    0x32, 0x10,
    0x40, 0x00, 0x00, 0x00,
    0x50, 0x00, 0x00, 0x00,
    0x60, 0x00, 0x00, 0x00,
    0x70, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);
  pw::Vector<uint32_t, 8> uint32s{};
  Status status = repeated_test.ReadUint32s(uint32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(uint32s.size(), 4u);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kUint32s);
  status = repeated_test.ReadUint32s(uint32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(uint32s.size(), 8u);

  for (int i = 0; i < 8; ++i) {
    EXPECT_EQ(uint32s[i], i * 16u);
  }

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);
  pw::Vector<uint32_t, 8> fixed32s{};
  status = repeated_test.ReadFixed32s(fixed32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(fixed32s.size(), 4u);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kFixed32s);
  status = repeated_test.ReadFixed32s(fixed32s);
  EXPECT_EQ(status, OkStatus());
  EXPECT_EQ(fixed32s.size(), 8u);

  for (int i = 0; i < 8; ++i) {
    EXPECT_EQ(fixed32s[i], i * 16u);
  }

  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
}

TEST(CodegenRepeated, NonScalar) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // strings[], v={"the", "quick", "brown", "fox"}
    0x1a, 0x03, 't', 'h', 'e',
    0x1a, 0x5, 'q',  'u', 'i', 'c', 'k',
    0x1a, 0x5,  'b', 'r', 'o', 'w',  'n',
    0x1a, 0x3, 'f', 'o', 'x'
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  constexpr std::array<std::string_view, 4> kExpectedString{
      {{"the"}, {"quick"}, {"brown"}, {"fox"}}};

  for (int i = 0; i < 4; ++i) {
    EXPECT_EQ(repeated_test.Next(), OkStatus());
    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kStrings);
    std::array<char, 32> string{};
    StatusWithSize sws = repeated_test.ReadStrings(string);
    EXPECT_EQ(sws.status(), OkStatus());
    EXPECT_EQ(sws.size(), kExpectedString[i].size());
    EXPECT_EQ(std::memcmp(string.data(),
                          kExpectedString[i].data(),
                          kExpectedString[i].size()),
              0);
  }

  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
}

TEST(CodegenRepeated, PackedEnum) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // enums[], v={RED, GREEN, AMBER, RED}
    0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kEnums);
  std::array<Enum, 4> enums{};
  StatusWithSize sws = repeated_test.ReadEnums(enums);
  EXPECT_EQ(sws.status(), OkStatus());
  ASSERT_EQ(sws.size(), 4u);

  for (int i = 0; i < 4; ++i) {
    EXPECT_TRUE(IsValidEnum(enums[i]));
  }

  EXPECT_EQ(enums[0], Enum::RED);
  EXPECT_EQ(enums[1], Enum::GREEN);
  EXPECT_EQ(enums[2], Enum::AMBER);
  EXPECT_EQ(enums[3], Enum::RED);

  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
}

TEST(CodegenRepeated, PackedEnumVector) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // enums[], v={RED, GREEN, AMBER, RED}
    0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  EXPECT_EQ(repeated_test.Next(), OkStatus());
  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::kEnums);
  pw::Vector<Enum, 4> enums{};
  Status status = repeated_test.ReadEnums(enums);
  EXPECT_EQ(status, OkStatus());
  ASSERT_EQ(enums.size(), 4u);

  for (int i = 0; i < 4; ++i) {
    EXPECT_TRUE(IsValidEnum(enums[i]));
  }

  EXPECT_EQ(enums[0], Enum::RED);
  EXPECT_EQ(enums[1], Enum::GREEN);
  EXPECT_EQ(enums[2], Enum::AMBER);
  EXPECT_EQ(enums[3], Enum::RED);

  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
}

}  // namespace
}  // namespace pw::protobuf
